package fixedrate

import (
    "context"
    "fmt"
    "time"

    "google.golang.org/protobuf/types/known/timestamppb"

    "cing.io/cing-api/planner"
    "cing.io/cing-api/task"
    "cing.io/cing/pkg/util"
)

type CingPlannerServer struct {
    planner.UnimplementedCingPlannerServer
}

func (CingPlannerServer) TaskTypeEcho(context.Context, *planner.TaskTypeEchoRequest) (*planner.TaskTypeEchoResponse, error) {
    return &planner.TaskTypeEchoResponse{
        Ok:       true,
        TaskType: TaskType(),
    }, nil
}

func (CingPlannerServer) OnTaskCreate(_ context.Context, req *planner.OnTaskCreateRequest) (*planner.OnTaskCreateResponse, error) {
    if err := checkTaskSpec(req.Task); err != nil {
        return &planner.OnTaskCreateResponse{
            TaskSpecCheckResult: &planner.TaskSpecCheckResult{ErrorMsg: err.Error()},
            NextPlan:            nil,
        }, nil
    }

    plan, err := generatePlanAtTime(req.Task, timestamppb.Now())
    if err != nil {
        return nil, err
    }
    return &planner.OnTaskCreateResponse{
        TaskSpecCheckResult: &planner.TaskSpecCheckResult{Ok: true},
        NextPlan:            plan,
    }, nil
}

func (CingPlannerServer) OnTaskUpdate(_ context.Context, req *planner.OnTaskUpdateRequest) (*planner.OnTaskUpdateResponse, error) {
    if err := checkTaskSpec(req.Task); err != nil {
        return &planner.OnTaskUpdateResponse{
            TaskSpecCheckResult: &planner.TaskSpecCheckResult{ErrorMsg: err.Error()},
            NextPlan:            nil,
        }, nil
    }

    plan, err := generatePlanAtTime(req.Task, timestamppb.Now())
    if err != nil {
        return nil, err
    }
    return &planner.OnTaskUpdateResponse{
        TaskSpecCheckResult: &planner.TaskSpecCheckResult{Ok: true},
        NextPlan:            plan,
        CancelPlan: &planner.CancelPlan{
            TaskFullName: util.GetTaskFullName(req.Task),
            PlanSelector: &planner.CancelPlan_SelectLastPlan{SelectLastPlan: &planner.PlanSelector_LastPlan{}},
        },
    }, nil
}

func (CingPlannerServer) OnTaskDelete(_ context.Context, req *planner.OnTaskDeleteRequest) (*planner.OnTaskDeleteResponse, error) {
    return &planner.OnTaskDeleteResponse{
        NextPlan: nil,
        CancelPlan: &planner.CancelPlan{
            TaskFullName: util.GetTaskFullName(req.Task),
            PlanSelector: &planner.CancelPlan_SelectLastPlan{SelectLastPlan: &planner.PlanSelector_LastPlan{}},
        },
    }, nil
}

func (CingPlannerServer) OnSingingStart(_ context.Context, req *planner.OnSingingStartRequest) (*planner.OnSingingStartResponse, error) {
    plan, err := generateNextPlan(req.Task, req.ThisPlan.StartTime)
    if err != nil {
        return nil, err
    }
    return &planner.OnSingingStartResponse{NextPlan: plan}, nil
}

func (CingPlannerServer) OnSingingEnd(_ context.Context, req *planner.OnSingingEndRequest) (*planner.OnSingingEndResponse, error) {
    if req.Successful {
        // do nothing
        return &planner.OnSingingEndResponse{}, nil
    }

    list := req.ThisPlan.ExcludedSingerIpList

    if list != nil {
        retries := req.ThisPlan.RetryTimes
        limit := util.Str2int32(req.Task.Spec[retryTimesLimit])
        if retries >= limit {
            // do not retry
            return &planner.OnSingingEndResponse{}, nil
        }
    }

    return &planner.OnSingingEndResponse{
        NextPlan: &planner.SingingPlan{
            TaskFullName:         util.GetTaskFullName(req.Task),
            StartTime:            timestamppb.New(time.Now()),
            SingerSelector:       req.ThisPlan.SingerSelector,
            ExcludedSingerIpList: append(req.ThisPlan.ExcludedSingerIpList, req.SingerIp),
            IsRetryPlan:          true,
            RetryTimes:           req.ThisPlan.RetryTimes + 1,
        },
    }, nil
}

func (CingPlannerServer) ListenEvents(*planner.ListenEventsRequest, planner.CingPlanner_ListenEventsServer) error {
    return nil
}

func checkTaskSpec(task *task.Task) error {
    schedule, ok := task.Spec[schedule]
    if !ok {
        return fmt.Errorf("spec field %s is not found", schedule)
    }
    if _, err := util.Str2int32WithError(schedule); err != nil {
        return err
    }

    retryLimit, ok := task.Spec[retryTimesLimit]
    if !ok {
        return fmt.Errorf("spec field %s is not found", schedule)
    }
    if _, err := util.Str2int32WithError(retryLimit); err != nil {
        return err
    }

    return nil
}

func generatePlanAtTime(task *task.Task, time *timestamppb.Timestamp) (*planner.SingingPlan, error) {
    plan := &planner.SingingPlan{
        TaskFullName:   util.GetTaskFullName(task),
        StartTime:      time,
        SingerSelector: nil,
    }

    // set selector
    util.ParseAndSetSingerSelector(task, plan)

    return plan, nil
}

func generateNextPlan(task *task.Task, startTime *timestamppb.Timestamp) (*planner.SingingPlan, error) {
    // get next time to singing
    next := startTime.AsTime().Add(time.Duration(util.Str2int32(task.Spec[schedule])) * time.Second)

    plan := &planner.SingingPlan{
        TaskFullName:   util.GetTaskFullName(task),
        StartTime:      timestamppb.New(next),
        SingerSelector: nil,
    }

    // set selector
    util.ParseAndSetSingerSelector(task, plan)

    return plan, nil
}
